package com.secure.support.feign;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.secure.annotation.ApiSecure;
import com.secure.bean.AppKey;
import com.secure.bean.SecureRequest;
import com.secure.bean.UserKey;
import com.secure.constant.RequestConstant;
import com.secure.exception.ApiSecureException;
import com.secure.handler.EncryptDecryptHandler;
import com.secure.handler.SignatureHandler;
import com.secure.properties.SecureClientProperties;
import com.secure.util.RequestUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * feign请求拦截器
 */
@Slf4j
public class FeignRequestSecureInterceptor implements RequestInterceptor {

    private final SecureClientProperties secureClientProperties;
    private final SignatureHandler signatureHandler;
    private final EncryptDecryptHandler encryptDecryptHandler;
    private UserKey userKey;
    private AppKey appKey;

    public FeignRequestSecureInterceptor(SecureClientProperties secureClientProperties, SignatureHandler signatureHandler, EncryptDecryptHandler encryptDecryptHandler) {
        this.secureClientProperties = secureClientProperties;
        this.signatureHandler = signatureHandler;
        this.encryptDecryptHandler = encryptDecryptHandler;
    }

    @Override
    public void apply(RequestTemplate requestTemplate) {
        if (isNeedSecureRequest(requestTemplate)) {
            secureRequest(requestTemplate);
        }
    }

    /**
     * 判断是否需要安全请求
     *
     * @param requestTemplate 请求模板
     * @return
     */
    private boolean isNeedSecureRequest(RequestTemplate requestTemplate) {
        Method method = requestTemplate.methodMetadata().method();
        if (method.isAnnotationPresent(ApiSecure.class)) {
            String serviceName = requestTemplate.feignTarget().name();
            if (StrUtil.isBlank(serviceName)) {
                throw new ApiSecureException("消费方：Feign客户端注解需要指定服务名");
            }
            AppKey appKey = secureClientProperties.getAppKeyConfig().get(serviceName);
            UserKey userKey = secureClientProperties.getUserKey();
            validateSecureConfig(userKey, appKey);
            this.appKey = appKey;
            this.userKey = userKey;
            return true;
        }
        return false;
    }

    /**
     * 校验安全配置
     *
     * @param userKey 服务消费方密钥配置
     * @param appKey  服务提供方密钥配置
     * @return
     */
    private void validateSecureConfig(UserKey userKey, AppKey appKey) {
        if (ObjectUtil.isNull(userKey)) {
            throw new ApiSecureException("消费方：消费方安全配置未配置");
        }
        if (StrUtil.isBlank(userKey.getUserId())) {
            throw new ApiSecureException("消费方：消费方标识未配置");
        }
        if (ObjectUtil.isNull(userKey.getSignature()) || StrUtil.isBlank(userKey.getSignature().getPrivateKey())) {
            throw new ApiSecureException("消费方：消费方签名私钥未配置");
        }
        if (ObjectUtil.isNull(appKey)) {
            throw new ApiSecureException("消费方：服务方安全配置未配置");
        }
        if (StrUtil.isBlank(appKey.getAppId())) {
            throw new ApiSecureException("消费方：服务方标识未配置");
        }
        if (ObjectUtil.isNull(appKey.getEncryptDecrypt()) || StrUtil.isBlank(appKey.getEncryptDecrypt().getPublicKey())) {
            throw new ApiSecureException("消费方：服务方提供的加密请求/解密响应的公钥未配置");
        }
    }

    /**
     * 验签并加密安全请求
     *
     * @param requestTemplate 请求模板
     * @throws IOException
     */
    private void secureRequest(RequestTemplate requestTemplate) {
        try {
            byte[] bodyBytes = requestTemplate.body();
            Map<String, Collection<String>> queries = requestTemplate.queries();
            //body请求体传参
            if (ArrayUtil.isNotEmpty(bodyBytes)) {
                String encryptText;
                Class<?> bodyParameterType = requestTemplate.methodMetadata().method().getParameterTypes()[0];
                if (String.class.equals(bodyParameterType)) {
                    String plainBody = new String(bodyBytes, StandardCharsets.UTF_8);
                    log.info("原请求参数{}", plainBody);

                    //加签、加密处理
                    Map<String, Object> plainMap = new HashMap<>();
                    plainMap.put(RequestConstant.STRING_BODY, plainBody);
                    String signature = signatureHandler.sign(plainMap, userKey.getSignature().getPrivateKey());
                    plainMap.put(RequestConstant.SIGNATURE, signature);
                    encryptText = encryptDecryptHandler.encryptByPublicKey(plainMap, appKey.getEncryptDecrypt().getPublicKey());
                    if (isXWwwFormUrlencodedContentType(requestTemplate)) {
                        encryptText = URLEncoder.encode(encryptText, StandardCharsets.UTF_8.toString());
                    }
                    log.info("加签请求参数{}", plainMap);
                } else {
                    Map<String, Object> plainMap = JSONUtil.toBean(new String(bodyBytes, StandardCharsets.UTF_8), new TypeReference<HashMap<String, Object>>() {
                    }, true);
                    log.info("原请求参数{}", plainMap);

                    //加签、加密处理
                    String signature = signatureHandler.sign(plainMap, userKey.getSignature().getPrivateKey());
                    plainMap.put(RequestConstant.SIGNATURE, signature);
                    encryptText = encryptDecryptHandler.encryptByPublicKey(plainMap, appKey.getEncryptDecrypt().getPublicKey());
                    log.info("加签请求参数{}", plainMap);
                }

                //封装安全请求
                SecureRequest secureRequest = new SecureRequest();
                secureRequest.setUserId(userKey.getUserId());
                secureRequest.setAppId(appKey.getAppId());
                secureRequest.setTimestamp(Instant.now().getEpochSecond());
                secureRequest.setData(encryptText);
                log.info("安全请求参数{}", secureRequest);
                requestTemplate.body(JSONUtil.toJsonStr(secureRequest));
            }
            //parameter传参
            else if (CollectionUtil.isNotEmpty(queries)) {
                Map<String, Object> plainMap = new HashMap<>();
                for (Map.Entry<String, Collection<String>> entry : queries.entrySet()) {
                    Collection<String> parameterValues = entry.getValue();
                    plainMap.put(entry.getKey(), CollectionUtil.isNotEmpty(parameterValues) ? URLDecoder.decode(parameterValues.iterator().next(), StandardCharsets.UTF_8.toString()) : "");
                }
                log.info("原请求参数{}", plainMap);

                String signature = signatureHandler.sign(plainMap, userKey.getSignature().getPrivateKey());
                plainMap.put(RequestConstant.SIGNATURE, signature);
                String encryptText = encryptDecryptHandler.encryptByPublicKey(plainMap, appKey.getEncryptDecrypt().getPublicKey());
                encryptText = URLEncoder.encode(encryptText, StandardCharsets.UTF_8.toString());
                log.info("加签请求参数{}", plainMap);

                //封装安全请求
                Map<String, Collection<String>> newQueries = new HashMap<>(3);
                newQueries.put(RequestConstant.USERID, Collections.singletonList(userKey.getUserId()));
                newQueries.put(RequestConstant.APPID, Collections.singletonList(appKey.getAppId()));
                newQueries.put(RequestConstant.TIMESTAMP, Collections.singletonList(String.valueOf(Instant.now().getEpochSecond())));
                newQueries.put(RequestConstant.DATA, Collections.singletonList(encryptText));
                log.info("安全请求参数{}", newQueries);
                requestTemplate.queries(null);
                requestTemplate.queries(newQueries);
            }

            //表单请求加Boundary
            handleRequestContentType(requestTemplate);

            //TODO 空参暂不处理
        } catch (Exception e) {
            throw new ApiSecureException("消费方：安全请求处理异常", e);
        }
    }

    /**
     * 判断是否是x-www-form-urlencoded内容类型
     *
     * @param requestTemplate
     * @return
     */
    private boolean isXWwwFormUrlencodedContentType(RequestTemplate requestTemplate) {
        Collection<String> contentTypes = requestTemplate.headers().get(RequestConstant.CONTENT_TYPE);
        return CollectionUtil.isNotEmpty(contentTypes) && contentTypes.stream().anyMatch(contentType -> contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
    }

    /**
     * 处理请求内容类型
     *
     * @param requestTemplate 请求模板
     */
    private void handleRequestContentType(RequestTemplate requestTemplate) {
        Collection<String> contentTypes = requestTemplate.headers().get(RequestConstant.CONTENT_TYPE);
        if (CollectionUtil.isNotEmpty(contentTypes) && contentTypes.stream().anyMatch(contentType -> contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE))) {
            requestTemplate.header(RequestConstant.CONTENT_TYPE, RequestUtil.getFormDataContentTypeWithBoundary());
        }
    }
}
